package com.judge.service.impl;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.common.core.constants.Constants;
import com.common.core.constants.JudgeConstants;
import com.common.core.enums.CodeRunStatus;
import com.judge.callback.DockerStartResultCallback;
import com.judge.callback.StatisticsCallback;
import com.judge.config.DockerSandBoxPool;
import com.judge.domain.CompileResult;
import com.judge.domain.SandBoxExecuteResult;
import com.judge.service.ISandboxService;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SandboxServiceImpl implements ISandboxService {

    @Value("${sandbox.docker.host:tcp://localhost:2375}")
    private String dockerHost;

    @Value("${sandbox.limit.memory:268435456}") // 256MB
    private Long memoryLimit;

    @Value("${sandbox.limit.memory-swap:268435456}") // 256MB
    private Long memorySwapLimit;

    @Value("${sandbox.limit.cpu:1}")
    private Long cpuLimit;

    @Value("${sandbox.limit.time:3}") // 3秒
    private Long timeLimit;

    @Autowired
    private DockerSandBoxPool dockerSandBoxPool;

    private String containerId;

    private String userCodeDir;

    private String userCodeFileName;
    
    private String containerRelativePath;

    @Override
    public SandBoxExecuteResult exeJavaCode(Long userId, String userCode, List<String> inputList) {
        try {
            // 先从容器池获取容器
            containerId = dockerSandBoxPool.getContainer();
            log.info("从容器池获取容器: {}", containerId);
            
            // 验证容器ID是否有效
            if (containerId == null || containerId.trim().isEmpty()) {
                throw new RuntimeException("无法获取有效的容器ID");
            }
            
            // 再创建用户代码文件
            createUserCodeFile(userId, userCode);
            
            //编译代码
            CompileResult compileResult = compileCodeByDocker();
            if (!compileResult.isCompiled()) {
                returnContainer();
                deleteUserCodeFile();
                return SandBoxExecuteResult.fail(CodeRunStatus.COMPILE_FAILED, compileResult.getExeMessage());
            }
            //执行代码
            SandBoxExecuteResult result = executeJavaCodeByDocker(inputList);
            returnContainer();
            deleteUserCodeFile();
            return result;
        } catch (Exception e) {
            log.error("代码执行失败: {}", e.getMessage(), e);
            returnContainer();
            deleteUserCodeFile();
            throw new RuntimeException("代码执行失败", e);
        }
    }


    //创建并返回用户代码的文件
    private void createUserCodeFile(Long userId, String userCode) {
        try {
            // 为每个用户每次提交创建独立的代码目录
            String time = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
            String uniqueDirName = userId + "_" + time + "_" + System.currentTimeMillis();
            
            // 在容器池代码目录下创建用户专属目录
            String containerCodeDir = dockerSandBoxPool.getCodeDir(containerId);
            userCodeDir = containerCodeDir + File.separator + uniqueDirName;
            
            log.info("创建用户专属代码目录: {}", userCodeDir);
            
            // 确保用户代码目录存在
            if (!FileUtil.exist(userCodeDir)) {
                FileUtil.mkdir(userCodeDir);
                log.info("用户代码目录创建成功: {}", userCodeDir);
            }
            
            userCodeFileName = userCodeDir + File.separator + JudgeConstants.USER_CODE_JAVA_CLASS_NAME;
            log.info("用户代码文件路径: {}", userCodeFileName);
            
            FileUtil.writeString(userCode, userCodeFileName, Constants.UTF8);
            log.info("用户代码文件写入成功");
            
            // 验证文件是否真的被创建
            if (FileUtil.exist(userCodeFileName)) {
                log.info("文件创建验证成功，文件大小: {} bytes", new File(userCodeFileName).length());
            } else {
                log.error("文件创建失败！文件不存在: {}", userCodeFileName);
            }
            
            // 存储容器内的相对路径，用于Docker命令执行
            containerRelativePath = uniqueDirName;
            log.info("容器内相对路径: {}", containerRelativePath);
            
            // 测试挂载是否正常工作
            testMountConnection();
            
        } catch (Exception e) {
            log.error("创建用户代码文件失败: {}", e.getMessage(), e);
            throw new RuntimeException("创建用户代码文件失败", e);
        }
    }
    
    /**
     * 测试挂载连接是否正常工作
     */
    private void testMountConnection() {
        try {
            // 在容器内创建一个测试文件
            String[] testCmd = new String[]{"touch", "/usr/share/java/test-mount.txt"};
            String cmdId = createExecCmd(testCmd, null, containerId);
            
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            dockerSandBoxPool.getDockerClient().execStartCmd(cmdId)
                    .exec(resultCallback)
                    .awaitCompletion();
            
            // 检查宿主机上是否能看到这个文件
            String testFilePath = dockerSandBoxPool.getCodeDir(containerId) + File.separator + "test-mount.txt";
            if (FileUtil.exist(testFilePath)) {
                log.info("挂载测试成功！容器内文件已同步到宿主机: {}", testFilePath);
                // 清理测试文件
                FileUtil.del(testFilePath);
            } else {
                log.warn("挂载测试失败！容器内文件未同步到宿主机: {}", testFilePath);
            }
        } catch (Exception e) {
            log.warn("挂载测试异常: {}", e.getMessage());
        }
    }

    private void returnContainer() {
        if (containerId != null) {
            log.info("归还容器到容器池: {}", containerId);
            dockerSandBoxPool.returnContainer(containerId);
            containerId = null;
            containerRelativePath = null;
        }
    }



    //编译
    //的使用docker编译
    private CompileResult compileCodeByDocker() {
        // 使用正确的文件路径进行编译
        String[] compileCmd = new String[]{"javac", "/usr/share/java/" + containerRelativePath + "/Solution.java"};
        log.info("开始编译，命令：{}", String.join(" ", compileCmd));
        log.info("编译前检查文件是否存在：{}", userCodeFileName);
        
        // 编译前再次检查源文件是否存在
        if (!FileUtil.exist(userCodeFileName)) {
            log.error("编译失败：源文件不存在 {}", userCodeFileName);
            CompileResult compileResult = new CompileResult();
            compileResult.setCompiled(false);
            compileResult.setExeMessage("源文件不存在：" + userCodeFileName);
            return compileResult;
        }
        
        String cmdId = createExecCmd(compileCmd, null, containerId);
        DockerStartResultCallback resultCallback = new DockerStartResultCallback();
        CompileResult compileResult = new CompileResult();
        try {
            dockerSandBoxPool.getDockerClient().execStartCmd(cmdId)
                    .exec(resultCallback)
                    .awaitCompletion();
            
            log.info("编译完成，状态：{}", resultCallback.getCodeRunStatus());
            log.info("编译错误信息：{}", resultCallback.getErrorMessage());
            log.info("编译输出信息：{}", resultCallback.getMessage());
            
            if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                compileResult.setCompiled(false);
                compileResult.setExeMessage(resultCallback.getErrorMessage());
            } else {
                compileResult.setCompiled(true);
            }
            return compileResult;
        } catch (InterruptedException e) {
            log.error("编译过程被中断：{}", e.getMessage(), e);
            //此处可以直接抛出 已做统一异常处理  也可再做定制化处理
            throw new RuntimeException(e);
        }
    }

    private SandBoxExecuteResult executeJavaCodeByDocker(List<String> inputList) {
        List<String> outList = new ArrayList<>(); //记录输出结果
        long maxMemory = 0L;  //最大占用内存
        long maxUseTime = 0L; //最大运行时间
        //执行用户代码
        for (String inputArgs : inputList) {
            // 使用正确的classpath和类名
            String[] execCmd = new String[]{"java", "-cp", "/usr/share/java/" + containerRelativePath, "Solution"};
            String cmdId = createExecCmd(execCmd, inputArgs, containerId);
            //执行代码
            StopWatch stopWatch = new StopWatch();        //执行代码后开始计时
            //执行情况监控
            StatsCmd statsCmd = dockerSandBoxPool.getDockerClient().statsCmd(containerId); //启动监控
            StatisticsCallback statisticsCallback = statsCmd.exec(new StatisticsCallback());
            stopWatch.start();
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            boolean completed = false;
            try {
                completed = dockerSandBoxPool.getDockerClient().execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(timeLimit, TimeUnit.SECONDS);
                        
                // 检查是否超时
                if (!completed) {
                    log.warn("代码执行超时: 超过{}s时间限制", timeLimit);
                    return SandBoxExecuteResult.fail(CodeRunStatus.OUT_OF_TIME, "代码执行超时");
                }
                
                if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                    //未通过所有用例返回结果
                    return SandBoxExecuteResult.fail(CodeRunStatus.NOT_ALL_PASSED);
                }
            } catch (InterruptedException e) {
                log.error("代码执行被中断: {}", e.getMessage());
                Thread.currentThread().interrupt(); // 恢复中断状态
                throw new RuntimeException("代码执行被中断", e);
            }
            stopWatch.stop();  //结束时间统计
            statsCmd.close();  //结束docker容器执行统计
            long userTime = stopWatch.getLastTaskTimeMillis(); //执行耗时
            maxUseTime = Math.max(userTime, maxUseTime);       //记录最大的执行用例耗时
            Long memory = statisticsCallback.getMaxMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, statisticsCallback.getMaxMemory()); //记录最大的执行用例占用内存
            }
            // 安全处理输出结果，避免空指针异常
            String output = resultCallback.getMessage();
            if (output != null) {
                outList.add(output.trim());
            } else {
                log.warn("执行结果为空，使用空字符串");
                outList.add("");
            }
        }
        // 注意：不再删除容器，容器会被归还到容器池中复用
        return getSanBoxResult(inputList, outList, maxMemory, maxUseTime); //封装结果
    }

    private String createExecCmd(String[] javaCmdArr, String inputArgs, String containerId) {
        // 直接使用传入的命令，因为路径已经在调用处处理过了
        String[] modifiedCmd = javaCmdArr.clone();
        
        if (!StrUtil.isEmpty(inputArgs)) {
            //当入参不为空时拼接入参
            String[] inputArray = inputArgs.split(" "); //入参
            modifiedCmd = ArrayUtil.append(modifiedCmd, inputArray);
        }
        
        log.info("执行Docker命令: {}", String.join(" ", modifiedCmd));
        
        // 使用容器池的DockerClient
        ExecCreateCmdResponse cmdResponse = dockerSandBoxPool.getDockerClient().execCreateCmd(containerId)
                .withCmd(modifiedCmd)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();
        return cmdResponse.getId();
    }

    private SandBoxExecuteResult getSanBoxResult(List<String> inputList, List<String> outList,
                                                 long maxMemory, long maxUseTime) {
        if (inputList.size() != outList.size()) {
            //输入用例数量 不等于 输出用例数量  属于执行异常
            return SandBoxExecuteResult.fail(CodeRunStatus.NOT_ALL_PASSED, outList, maxMemory, maxUseTime);
        }
        return SandBoxExecuteResult.success(CodeRunStatus.SUCCEED, outList, maxMemory, maxUseTime);
    }


    private void deleteUserCodeFile() {
        // 在删除用户代码文件之前，先保存判题结果到挂载目录
        saveJudgeResults();
        FileUtil.del(userCodeDir);
    }
    
    private void saveJudgeResults() {
        try {
            if (userCodeDir != null && FileUtil.exist(userCodeDir)) {
                // 创建判题结果目录
                String judgeResultsDir = System.getProperty("user.dir") + File.separator + "judge-results";
                if (!FileUtil.exist(judgeResultsDir)) {
                    FileUtil.mkdir(judgeResultsDir);
                }
                
                // 创建时间戳目录
                String timestamp = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
                String resultDir = judgeResultsDir + File.separator + timestamp + "_" + containerId;
                FileUtil.mkdir(resultDir);
                
                // 复制用户代码文件
                if (userCodeFileName != null && FileUtil.exist(userCodeFileName)) {
                    String targetCodeFile = resultDir + File.separator + "Solution.java";
                    FileUtil.copy(userCodeFileName, targetCodeFile, true);
                    log.info("用户代码已保存到: {}", targetCodeFile);
                }
                
                // 复制编译后的class文件（如果存在）
                String classFile = userCodeDir + File.separator + "Solution.class";
                if (FileUtil.exist(classFile)) {
                    String targetClassFile = resultDir + File.separator + "Solution.class";
                    FileUtil.copy(classFile, targetClassFile, true);
                    log.info("编译文件已保存到: {}", targetClassFile);
                }
                
                // 保存容器执行日志
                String logFile = resultDir + File.separator + "execution.log";
                String logContent = String.format("容器ID: %s\n用户代码目录: %s\n执行时间: %s\n", 
                        containerId, userCodeDir, timestamp);
                FileUtil.writeString(logContent, logFile, Constants.UTF8);
                log.info("执行日志已保存到: {}", logFile);
                
                log.info("判题结果已保存到挂载目录: {}", resultDir);
            }
        } catch (Exception e) {
            log.error("保存判题结果失败: {}", e.getMessage(), e);
        }
    }
}
